package com.snda.sc.config;

import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

import org.springframework.stereotype.Component;

import com.snda.sc.config.Configurable.Scope;
import com.snda.sc.exception.ConfigureException;
import com.snda.sc.utils.FileWatcher;
import com.snda.sc.utils.FileWatcher.FileWatcherAction;
import com.snda.sc.utils.Logs;

@Component("configRegistry")
public class ConfigRegistry implements IConfigRegistry {

	private Map<Set<String>, Configurable> globalConfigMap = new HashMap<>();
	private Map<Set<String>, Configurable> appConfigMap = new HashMap<>();

	@Resource(name = "globalConfDir")
	private Path globalConfDir;
	@Resource(name = "appConfDir")
	private Path appConfDir;

	private FileWatcher globalConfigWatcher;
	private FileWatcher appConfigWatcher;

	@PostConstruct
	public void init() {
		try {
			globalConfigWatcher = new FileWatcher(globalConfDir, createWatcherAction(globalConfigWatcher, globalConfigMap));
			appConfigWatcher = new FileWatcher(appConfDir, createWatcherAction(appConfigWatcher, appConfigMap));

			globalConfigWatcher.watch();
			appConfigWatcher.watch();
		} catch (IOException e) {
			throw new ConfigureException(e);
		}
	}

	private FileWatcherAction createWatcherAction(FileWatcher watcher, final Map<Set<String>, Configurable> map)
			throws IOException {
		return new FileWatcherAction() {
			@Override
			public void execute(Path modified) {
				String fileName = modified.getFileName().toString();
				for (Entry<Set<String>, Configurable> entry : map.entrySet()) {
					if (!entry.getKey().contains(fileName)) continue;
					Configurable config = entry.getValue();
					try {
						config.configure(modified.toFile());
						Logs.logSysInfo("Configuration file '{}' has modified, '{}' reloaded it successfully.", modified,
								config.getClass().getName());
					} catch (Exception e) {
						Logs.logException("Configuration file '" + modified + "' reload error.", e);
					}
				}
			}
		};
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.snda.sc.config.IConfigRegistry#registerConfig(com.snda.sc.config.
	 * Configurable)
	 */
	@Override
	public void registerConfig(Configurable config) {
		if (config.getScope() == Scope.GLOBAL) registerConfig(config, globalConfigMap, globalConfDir);
		else if (config.getScope() == Scope.APP) registerConfig(config, appConfigMap, appConfDir);
		else throw new ConfigureException("Unknow scope type: " + config.getScope());
	}

	private void registerConfig(Configurable config, Map<Set<String>, Configurable> map, Path confDir) {
		if (config.getConfigFileNames() == null || config.getConfigFileNames().size() <= 0) return;
		if (config.watchModify()) map.put(config.getConfigFileNames(), config);
		for (String configFileName : config.getConfigFileNames()) {
			Path configPath = confDir.resolve(configFileName);
			try {
				config.configure(configPath.toFile());
				Logs.logSysInfo("'{}' load configuration file '{}' successfully.", config.getClass().getName(), configPath);
			} catch (Exception e) {
				throw new ConfigureException(e);
			}
		}
	}

	@PreDestroy
	public void destroy() {
		try {
			globalConfigWatcher.destroy();
			appConfigWatcher.destroy();
		} catch (IOException e) {
			throw new ConfigureException(e);
		}

		globalConfigMap.clear();
		appConfigMap.clear();
	}
}
